Start by loading the various libraries and packages we will be using.

The “Pacman” package will automatically install any packages you do not currently have installed and load the applicable libraries.

if (!require("pacman")) install.packages("pacman")
pacman::p_load(dplyr, spdplyr, sf, sp, spdep, spatialEco, rgdal, geojsonio, geojsonR, leaflet, leaflet.minicharts, leaflet.extras,leafsync, maptools, tmap, tidyverse, ggplot, gclus, rpart)

Create our Data Tables

KSI Pedestrian Dataset

This dataset is a subset of the Killed and Seriously Injured (KSI) dataset collected by the Toronto Police Service from 2008-2018. These events include any serious or fatal collisions where a Pedestrian is involved. To learn more about Pedestrians related collisions in Toronto you can follow this link: http://data.torontopolice.on.ca/pages/pedestrians

We will use the FROM_GeoJson command from the “geojsonR” package to download a json file from the provided URL. The geojson_read command from the “geojsonio” and formating form the “sp” package can then be used to read the json and generate a SpatialPointsDataFrame

ksi_ped <- geojsonio::geojson_read("https://opendata.arcgis.com/datasets/3dedc9bff625450990b8d480f397ad3f_0.geojson", what = "sp")
head(ksi_ped)
class       : SpatialPointsDataFrame 
features    : 6 
extent      : -79.45759, -79.36952, 43.6681, 43.71057  (xmin, xmax, ymin, ymax)
crs         : +proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +towgs84=0,0,0 
variables   : 55
names       :   Index_,     ACCNUM, YEAR,                   DATE, TIME, Hour,        STREET1,       STREET2, OFFSET,     ROAD_CLASS,              District,  LATITUDE,  LONGITUDE,     LOCCOORD,          ACCLOC, ... 
min values  :  5413936,    1062115, 2008, 2008/09/02 04:00:00+00,  917,    9,    REDPATH AVE,  BROADWAY AVE,       ,      Collector, Toronto and East York, 43.668101,  -79.45759, Intersection, At Intersection, ... 
max values  : 80632109, 6001446484, 2016, 2016/08/16 04:00:00+00,  920,    9, WELLESLEY ST E, PARLIAMENT ST,       , Minor Arterial, Toronto and East York, 43.710574, -79.369525, Intersection, At Intersection, ... 

KSI TTC/Municipal Vehicle Dataset

This dataset is a subset of the Killed and Seriously Injured (KSI) dataset collected by the Toronto Police Service from 2008-2018. These events include any serious or fatal collision involving an operator or passenger of a TTC, Transit Vehicle, streetcar or Municipal Vehicle. To learn more about TTC-Municipal Vehicle related collisions in Toronto you can follow this link: http://data.torontopolice.on.ca/pages/ttc-municipal-vehicle

We will use the FROM_GeoJson command from the “geojsonR” package to download a json file from the provided URL. The geojson_read command from the “geojsonio” and formating form the “sp” package can then be used to read the json and generate a SpatialPointsDataFrame

ksi_ttc <- geojsonio::geojson_read("https://opendata.arcgis.com/datasets/dc4751278e604d65b0886b9765d4b551_0.geojson", what = "sp")
head(ksi_ttc)
class       : SpatialPointsDataFrame 
features    : 6 
extent      : -79.44409, -79.30189, 43.65105, 43.72284  (xmin, xmax, ymin, ymax)
crs         : +proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +towgs84=0,0,0 
variables   : 55
names       :  Index_,  ACCNUM, YEAR,                   DATE, TIME, Hour,           STREET1,     STREET2, OFFSET,     ROAD_CLASS,              District,  LATITUDE,  LONGITUDE,  LOCCOORD, ACCLOC, ... 
min values  : 6597284, 1220073, 2011, 2011/01/06 05:00:00+00, 1404,   14,        QUEEN ST W, DUFFERIN ST,       , Major Arterial,           Scarborough, 43.651045, -79.444089, Mid-Block,       , ... 
max values  : 7645393, 1333336, 2012, 2012/12/31 05:00:00+00, 1415,   14, VICTORIA PARK AVE,     YORK ST,       , Major Arterial, Toronto and East York, 43.722845,  -79.30189, Mid-Block,       , ... 

Merge KSI_TTC and KSI_Ped

As you will have noticed from the descritpions both the KSI_PED and KSI_TTC datasets are themselves subsets of the Toronto Police Services’ KSI Dataset. We downloaded them as seperate dataframes to enable faster downloads as they make up a small portion of the full KSI dataset, which would take significantly more time to download and sort. Because they come from the same base dataset and have the same schema we can merge them back into one dataframe to work with. We will merge them based on the “Index_” to ensure there are no duplicates created due to the merging process.

ksi_merged <- merge(ksi_ped,ksi_ttc, by="Index_")

Neighbourhood Boundaries - City of Toronto

The City of Toronto also maintains a list that defines the boundaries of all the neighbourhoods in the city. A file containing the spatial data required to map these neighbourhoods can be downloaded form the City of Toronto open data portal. Due to how this file will download, unlike the KSI data, we cannot directly read the GeoJson file but have to download it before the file can be read.

trying URL 'https://ckan0.cf.opendata.inter.prod-toronto.ca/download_resource/a083c865-6d60-4d1d-b6c6-b0c8a85f9c15?format=geojson&projection=4326'
Content type 'application/json' length unknown
downloaded 1.9 MB
class       : SpatialPolygonsDataFrame 
features    : 6 
extent      : -79.52174, -79.39119, 43.64744, 43.78728  (xmin, xmax, ymin, ymax)
coord. ref. : +proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +towgs84=0,0,0 
variables   : 15

Simply Analytics - Census Data

From Simply Analytics i have downloaded shapefiles containing average income by census track and subdivision. These two shapefiles will usefull in drilling down beyond the neighbourhood. Both shapefiles can be uploaded into a spatial dataframe using the st_read command from the “sf” package we installed earlier then converting them to a SpatialPolygonsDataFrame.

ogrInfo(dsn="datasets/SimplyAnalytics_C1", layer="C1")
Source: "C:\Users\gvs_j\Documents\GitHub\CSDA-1050F18S1\jacobgvs_304292\sprint_2\Datasets\SimplyAnalytics_C1", layer: "C1"
Driver: ESRI Shapefile; number of rows: 572 
Feature type: wkbPolygon with 2 dimensions
Extent: (-79.6393 43.56034) - (-79.11347 43.85547)
CRS: +proj=longlat +datum=WGS84 +no_defs  
LDID: 87 
Number of fields: 4 
ctr <- readOGR(dsn = "datasets/SimplyAnalytics_C1", layer = "C1")
OGR data source with driver: ESRI Shapefile 
Source: "C:\Users\gvs_j\Documents\GitHub\CSDA-1050F18S1\jacobgvs_304292\sprint_2\Datasets\SimplyAnalytics_C1", layer: "C1"
with 572 features
It has 4 fields
names(ctr@data)[names(ctr@data)=="VALUE0"] <- "Household Total Income After-Tax"
names(ctr@data)[names(ctr@data)=="VALUE1"] <- "Household Aggregate Income"
names(ctr@data)[names(ctr@data)=="VALUE2"] <- "Household Average Income"
head(ctr)
class       : SpatialPolygonsDataFrame 
features    : 6 
extent      : -79.47429, -79.30083, 43.61037, 43.66591  (xmin, xmax, ymin, ymax)
coord. ref. : +proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +towgs84=0,0,0 
variables   : 4
ogrInfo(dsn="datasets/SimplyAnalytics_C2", layer="C2")
Source: "C:\Users\gvs_j\Documents\GitHub\CSDA-1050F18S1\jacobgvs_304292\sprint_2\Datasets\SimplyAnalytics_C2", layer: "C2"
Driver: ESRI Shapefile; number of rows: 3702 
Feature type: wkbPolygon with 2 dimensions
Extent: (-79.6393 43.56034) - (-79.11347 43.85547)
CRS: +proj=longlat +datum=WGS84 +no_defs  
LDID: 87 
Number of fields: 5 
disem <- readOGR(dsn = "datasets/SimplyAnalytics_C2", layer = "C2")
OGR data source with driver: ESRI Shapefile 
Source: "C:\Users\gvs_j\Documents\GitHub\CSDA-1050F18S1\jacobgvs_304292\sprint_2\Datasets\SimplyAnalytics_C2", layer: "C2"
with 3702 features
It has 5 fields
names(disem@data)[names(disem@data)=="VALUE0"] <- "Household Total Income After-Tax"
names(disem@data)[names(disem@data)=="VALUE1"] <- "Household Aggregate Income"
names(disem@data)[names(disem@data)=="VALUE2"] <- "Household Average Income"
summary(disem)
Object of class SpatialPolygonsDataFrame
Coordinates:
        min       max
x -79.63930 -79.11347
y  43.56034  43.85547
Is projected: FALSE 
proj4string :
[+proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +towgs84=0,0,0]
Data attributes:
    spatial_id                    name      Household Total Income After-Tax
 35200002:   1   DA0002, Toronto, ON:   1   Min.   :   0.0                  
 35200003:   1   DA0003, Toronto, ON:   1   1st Qu.: 159.0                  
 35200004:   1   DA0004, Toronto, ON:   1   Median : 202.5                  
 35200005:   1   DA0005, Toronto, ON:   1   Mean   : 300.6                  
 35200006:   1   DA0006, Toronto, ON:   1   3rd Qu.: 310.0                  
 35200007:   1   DA0007, Toronto, ON:   1   Max.   :4889.0                  
 (Other) :3696   (Other)            :3696                                   
 Household Aggregate Income Household Average Income
 Min.   :        0          Min.   :      0         
 1st Qu.: 14441832          1st Qu.:  72481         
 Median : 20666158          Median :  90304         
 Mean   : 30880924          Mean   : 116745         
 3rd Qu.: 33046773          3rd Qu.: 120148         
 Max.   :534324916          Max.   :2340057         
                                                    
head(disem@data)

Additional Datatables

One additional datatable may be needed depending on how we choose to cluster the KSI data. At a high level grouping accidents to a given neighbourhood as defined by the city then using the census data to further cluster by census track and subdivision will be ideal. I may need to pull the Neighbourhood Profiles data set from the city of Toronto to get an average income level by neighbourhood as this does not appear to be avaialble directly from the census datasets.

Reviewing the Datasets

With our datasets downloaded we can start by looking through the data. Becasue we are dealing almost exclusively with spatial data the easiest way to get a handle on the data is by mapping it. To do this I will be using features from the “leaflet” package.

KSI_merged Dataset

Lets satrt by getting an idea of where accidents are occuring. The leaflet package will map the data for us. To make this more readable I have had the system autocluster the accidents. These clusters don’t have any relation to specific neighbourhoods and will need to be adjusted later so that the clusters are in line with our other datasets.

leaflet(ksi_merged) %>%
  addTiles() %>%
  addMarkers(lng = ksi_merged$LONGITUDE, lat = ksi_merged$LATITUDE, clusterOptions = markerClusterOptions())

This map will allow you to zoom in and the clusters will auto adjust as you zoom in and out. These clusters are based on proximity to a central point. Once you get to the lowest zoom levels, clicking on a cluster will map the individual accidents. Details for each accident are not currenlty included in the mapping.

Neighbourhood Boundaries - City of Toronto

Our next dataset contains the boundary lines for various neighbourhoods in Toronto. mapping this will begin to give some dimension to how we intend to cluster accidents going forward.

leaflet(nbh) %>%
  addTiles() %>%
  addPolygons()

Simply Analytics - Census Data

We can similarly plot both the census tract and dissemination area files pulled from Simply Analytics.

leaflet(ctr) %>%
  addTiles() %>%
  addPolygons()
leaflet(disem) %>%
  addTiles() %>%
  addPolygons()

Our next step will be to assing each accident in the KSI data to a dissemination area polygon. But before we can do that we will first have to clean up the various datasets.

Cleaning the Data

Before we begin we need to ensure that our various datasets are compatible.

Using the CRS fuction in the SF package We can check the coordinate reference system being used by our main files and can see that they are all the same.

st_crs(ksi_merged)
Coordinate Reference System:
  EPSG: 4326 
  proj4string: "+proj=longlat +datum=WGS84 +no_defs"
st_crs(ctr)
Coordinate Reference System:
  EPSG: 4326 
  proj4string: "+proj=longlat +datum=WGS84 +no_defs"
st_crs(disem)
Coordinate Reference System:
  EPSG: 4326 
  proj4string: "+proj=longlat +datum=WGS84 +no_defs"
st_crs(nbh)
Coordinate Reference System:
  EPSG: 4326 
  proj4string: "+proj=longlat +datum=WGS84 +no_defs"

We luckily all the files we have downloaded use the same co-ordinate reference system so we will be able to compare and associate them to one another without having to re-project them into a comon co-ordinate system. This is especially convenient given that the mapping package w are using “leaflet” does not support “CRS” one of the other reference systems.

Cleaning the KSI data

As we review the datasets one thing you may have noticed is that the KSI dataset contains a large number of potenitally duplicate entries.

head(ksi_merged@data, 20)

While each accident is assigned a different index id number(Index_), in most cases multiple index numbers are assigned to the same account number(ACCNUM). This is because, for each accident, the driver of the vehicle, the pedestrian, and any other applicable individual related to the loss has their information recorded to the dataset. This is most evident if you look at the involved vehicle type (INVTYPE) columns.

Since this review is focused on pedestrian accidents, we will filter our KSI_Merged file to include only instances where the column INVTYPE is a pedestrian.

We can easily identify all unique items in the INVTYPE column and select those that are representative of pedestrians.

unique(ksi_merged@data$INVTYPE)
 [1] Driver               Pedestrian           Vehicle Owner        Passenger           
 [5] Truck Driver         Other                Wheelchair           Pedestrian - Not Hit
 [9] Motorcycle Driver    Driver - Not Hit     Other Property Owner                     
[13] In-Line Skater       Runaway - No Driver  Cyclist              Witness             
[17] Trailer Owner       
17 Levels:   Cyclist Driver Driver - Not Hit In-Line Skater Motorcycle Driver ... Witness

If we look at our list of options, there are a few different entries we should be considering to be a pedestrian. For this review we will be filtering out all entries where the INVTYPE is “Pedestrian”, “Pedestrian - Not Hit”, “In-Line Skater”, or “Wheelchair”.

So as not to lose our merged dataset, we will also create a new filtered datatable. We can easily identify and, if required in the future, modify the list of items being filtered into our datatable by setting them as a target

target <- c("Pedestrian", "Pedestrian - Not Hit", "In-Line Skater", "Wheelchair")
ksi_modified <- ksi_merged[ksi_merged@data$INVTYPE %in% target,]

There are also a number of columns that will not be needed for our review and can be removed from our modified dataset. These include: OFFSET, VEHTYPE, MANOEUVER, DRIVACT, DRIVCOND, CYCLISTYPE, CYCACT, CYCCOND, CYCLIST, TRSN_CITY_VEH, TRSN_CITY_, coords.x1, coords.x2

target_coll <- c("OFFSET", "VEHTYPE", "MANOEUVER", "DRIVACT", "DRIVCOND", "CYCLISTYPE", "CYCACT", "CYCCOND", "CYCLIST", "TRSN_CITY_VEH", "TRSN_CITY_", "coords.x1", "coords.x2")
ksi_modified <- ksi_modified[,-which(names(ksi_modified@data) %in% c(target_coll))]
head(ksi_modified@data, 20)

Cleaning the Toronto Neighbourhood data

Next we will look at the Toronto Neighbourhood dataset.

head(nbh@data, 20)

We can see that for the most part all columns in this data set are useful. The columns that will not be of use and can be removed are: PARENT_AREA_ID - this field containe no unique values AREA_LONG_CODE - this is variable is identical to AREA_SHORT_CODE AREA_DESC - this is variable is identical to AREA_NAME X - this field containe no unique values Y - this field containe no unique values

unique(nbh@data$PARENT_AREA_ID)
[1] 49885
unique(nbh@data$X)
[1] <NA>
Levels: 
unique(nbh@data$Y)
[1] <NA>
Levels: 
targetnbh <- c("PARENT_AREA_ID", "AREA_LONG_CODE", "AREA_DESC", "X", "Y")
nbh_modified <- nbh
nbh_modified <- nbh_modified[,-which(names(nbh_modified@data) %in% c(targetnbh))]
head(nbh_modified@data, 20)

Cleaning the Census Tract and Disemination data

Next we will look at the Toronto Neighbourhood dataset.

head(ctr@data, 20)

We can see that these have a very minimal number of fields and will not require any adjustmeents prior to proceeding with our review.

Associating the Datasets

We are interested in associating all our spatial polygon dataframes to the KSI data points that represent each pedestrian related accident.

Our first task will be to join our KSI dataset to a Toronto Neighbourhood.

This can be done using the spatialEco package. The spatialEco::point.in.poly function intersects point and polygon feature classes and adds polygon attributes to points. This function will re-name columns with similar names so we will also address re-naming some of the newly added columns.

ksi_coord <- spatialEco::point.in.poly(ksi_modified, nbh_modified)
although coordinates are longitude/latitude, st_intersects assumes that they are planar
although coordinates are longitude/latitude, st_intersects assumes that they are planar
names(ksi_coord@data)[names(ksi_coord@data)=="LONGITUDE.x"] <- "LONGITUDE"
names(ksi_coord@data)[names(ksi_coord@data)=="LATITUDE.x"] <- "LATITUDE"
names(ksi_coord@data)[names(ksi_coord@data)=="LONGITUDE.y"] <- "LONGITUDE.nbh"
names(ksi_coord@data)[names(ksi_coord@data)=="LATITUDE.y"] <- "LATITUDE.nbh"
head(ksi_coord@data, 20)

Next we will add in the disemination area information. Being the most granular this will allow us to accurately associate an average income to each accident.

ksi_coord <- spatialEco::point.in.poly(ksi_coord, disem)
although coordinates are longitude/latitude, st_intersects assumes that they are planar
although coordinates are longitude/latitude, st_intersects assumes that they are planar
head(ksi_coord@data, 20)

Now that we have a assigned an average income to each accident we can start working to review our data and learn more abot what story it can tell.

Since we are trying to look at socioeconomiv impact on accidents we will first start by focusing on average income.

Lets start by creating a histogram of the Household Average Income information for our accidents.

qplot(ksi_coord$Household.Average.Income,
      geom="histogram",
      binwidth = 5,
      main = "Histogram Average Income",
      xlab = "Average Income",
      fill=I("blue"), 
      col=I("red"))

As we can see the majority of losses are clustered at the bottom end of the income scale. However this histogram can be deceiving. The average income varies so greatly between the lowest and highest values that it is hard to identify what income brakets this cluster actually represents.

We can re-present the same graph with a reduced bin width but it does little to make the data points more meaningful.

qplot(ksi_coord$Household.Average.Income,
      geom="histogram",
      main = "Histogram Average Income",
      xlab = "Average Income",
      fill=I("blue"), 
      col=I("red"))

To allow us to more easily review the data set we will also convert it from a SpatialPointsDataFrame to a regular dataframe. We can then pull some basic information about household income.

ksi_sf <- as.data.frame(ksi_coord@data)
mean(ksi_sf$Household.Average.Income, na.rm = TRUE)
[1] 100023
mode(ksi_sf$Household.Average.Income)
[1] "numeric"
range(ksi_sf$Household.Average.Income, na.rm = TRUE)
[1]       0 1065780
quantile(ksi_sf$Household.Average.Income, na.rm = TRUE)
       0%       25%       50%       75%      100% 
      0.0   64743.0   82846.0  108044.8 1065780.0 
quantile(ksi_sf$Household.Average.Income, na.rm = TRUE, probs = c(0.01, 0.025, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 0.75, 1.00))
        1%       2.5%         5%        10%        20%        30%        40%        50%        75% 
  28453.35   36927.38   44130.50   51818.00   61222.00   68192.00   75706.00   82846.00  108044.75 
      100% 
1065780.00 

The quantile results are very interesting in that we can see that the bottom 25% of incomes actually varies between $0 and $65,000 and the highest value is well beyond being plausibly compared to the rest

This does give some indication that we will probably want to focus on the bottom 50% of incomes depedning on how the income level corelates to frequency of accidents

ksi_sf <- as.data.frame(ksi_coord@data)

freq <- table(ksi_sf$YEAR, ksi_sf$AREA_NAME, ksi_sf$ROAD_CLASS, ksi_sf$Household.Average.Income)
yearfreq <- ksi_sf %>%
  group_by(YEAR) %>%
  summarise(freq = n())
yearfreq <- as.data.frame(yearfreq)
AreaLossFreq <- ksi_sf %>%
  group_by(AREA_NAME) %>%
  summarise(freq = n())
AreaLossFreq <- as.data.frame(AreaLossFreq)
RoadClassFreq <- ksi_sf %>%
  group_by(ROAD_CLASS) %>%
  summarise(freq = n())
RoadClassFreq <- as.data.frame(RoadClassFreq)
AveIncomeFreq <- ksi_sf %>%
  group_by(Household.Average.Income) %>%
  summarise(freq = n())
AveIncomeFreq <- as.data.frame(AveIncomeFreq)

A standard frequency plot will do much better at indicating the average income of the areas where these pedestrian accidents usually occur but again will not provide any relevant data

plot(AveIncomeFreq)

We will want to use the quantile results to set income bandwiths to apply to each of our accidents. As evident from the hitograms and frequency plot we will also want to look at focusing on the lower quantiles.

To do:

  1. Establish income bandwiths and associate back to main datatable.

  2. Continue with frequency revie and comparison of various variables.

  3. Correct clustering as part of the evaluation. Example below does not help given it cannot be read.

library(pvclust)
d <- dist(ksi_sf, method = "euclidean")
fit <- hclust(d, method="ward.D2")
plot(fit)
groups <- cutree(fit, k=5)
rect.hclust(fit, k=5, border="red")

  1. Review how to bind markers in leaflet to their polygon(s). Currently unclear if this feature is available in Rstudio or only available using the leaflet code html or leaflet in python
Lcontent <- paste("Year:", ksi_coord@data$YEAR,
                  "Income:",ksi_coord@data$Household.Average.Income, 
                  "Location:",
                  ksi_coord@data$STREET1)

leaflet(ksi_coord) %>%
  addTiles() %>%
  addCircleMarkers(
    stroke = FALSE, fillOpacity = 0.5,weight = 1,
    label = ~as.character(Lcontent),
    clusterOptions = markerClusterOptions()) %>%
  addPolygons(data = nbh_modified,
              fillColor = "transparent", 
              color = "#000000",
              fillOpacity = 0.8,
              group = "Neighbourhood", 
              weight = 2) %>%
  addPolygons(data = ctr,
              fillColor = "transparent", 
              color = "#000000",
              fillOpacity = 0.8,
              group = "Census Tract", 
              weight = .8) %>%
  addPolygons(data = disem,
              fillColor = "transparent", 
              color = "#000000",
              fillOpacity = 0.8,
              group = "Disemination Area", 
              weight = .4) %>%
  addLayersControl(overlayGroups =c("Neighbourhood", "Census Tract", "Disemination Area"),
                   options = layersControlOptions(collapsed=FALSE))

NA
LS0tDQp0aXRsZTogIlNvY2lvZWNvbm9taWNzIG9mIFBlZGVzdHJpYW4gQWNjaWRlbnRzIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KDQpTdGFydCBieSBsb2FkaW5nIHRoZSB2YXJpb3VzIGxpYnJhcmllcyBhbmQgcGFja2FnZXMgd2Ugd2lsbCBiZSB1c2luZy4NCg0KVGhlICJQYWNtYW4iIHBhY2thZ2Ugd2lsbCBhdXRvbWF0aWNhbGx5IGluc3RhbGwgYW55IHBhY2thZ2VzIHlvdSBkbyBub3QgY3VycmVudGx5IGhhdmUgaW5zdGFsbGVkIGFuZCBsb2FkIHRoZSBhcHBsaWNhYmxlIGxpYnJhcmllcy4NCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZXJyb3I9RkFMU0V9DQppZiAoIXJlcXVpcmUoInBhY21hbiIpKSBpbnN0YWxsLnBhY2thZ2VzKCJwYWNtYW4iKQ0KcGFjbWFuOjpwX2xvYWQoZHBseXIsIHNwZHBseXIsIHNmLCBzcCwgc3BkZXAsIHNwYXRpYWxFY28sIHJnZGFsLCBnZW9qc29uaW8sIGdlb2pzb25SLCBsZWFmbGV0LCBsZWFmbGV0Lm1pbmljaGFydHMsIGxlYWZsZXQuZXh0cmFzLGxlYWZzeW5jLCBtYXB0b29scywgdG1hcCwgdGlkeXZlcnNlLCBnZ3Bsb3QsIGdjbHVzLCBycGFydCkNCmBgYA0KDQoNCiMjIENyZWF0ZSBvdXIgRGF0YSBUYWJsZXMNCg0KIyMjIyBLU0kgUGVkZXN0cmlhbiBEYXRhc2V0DQoNClRoaXMgZGF0YXNldCBpcyBhIHN1YnNldCBvZiB0aGUgS2lsbGVkIGFuZCBTZXJpb3VzbHkgSW5qdXJlZCAoS1NJKSBkYXRhc2V0IGNvbGxlY3RlZCBieSB0aGUgVG9yb250byBQb2xpY2UgU2VydmljZSBmcm9tIDIwMDgtMjAxOC4gVGhlc2UgZXZlbnRzIGluY2x1ZGUgYW55IHNlcmlvdXMgb3IgZmF0YWwgY29sbGlzaW9ucyB3aGVyZSBhIFBlZGVzdHJpYW4gaXMgaW52b2x2ZWQuIFRvIGxlYXJuIG1vcmUgYWJvdXQgUGVkZXN0cmlhbnMgcmVsYXRlZCBjb2xsaXNpb25zIGluIFRvcm9udG8geW91IGNhbiBmb2xsb3cgdGhpcyBsaW5rOiBodHRwOi8vZGF0YS50b3JvbnRvcG9saWNlLm9uLmNhL3BhZ2VzL3BlZGVzdHJpYW5zDQoNCldlIHdpbGwgdXNlIHRoZSBGUk9NX0dlb0pzb24gY29tbWFuZCBmcm9tIHRoZSAiZ2VvanNvblIiIHBhY2thZ2UgdG8gZG93bmxvYWQgYSBqc29uIGZpbGUgZnJvbSB0aGUgcHJvdmlkZWQgVVJMLiBUaGUgZ2VvanNvbl9yZWFkIGNvbW1hbmQgZnJvbSB0aGUgImdlb2pzb25pbyIgYW5kIGZvcm1hdGluZyBmb3JtIHRoZSAic3AiIHBhY2thZ2UgY2FuIHRoZW4gYmUgdXNlZCB0byByZWFkIHRoZSBqc29uIGFuZCBnZW5lcmF0ZSBhIFNwYXRpYWxQb2ludHNEYXRhRnJhbWUgDQpgYGB7cn0NCmtzaV9wZWQgPC0gZ2VvanNvbmlvOjpnZW9qc29uX3JlYWQoImh0dHBzOi8vb3BlbmRhdGEuYXJjZ2lzLmNvbS9kYXRhc2V0cy8zZGVkYzliZmY2MjU0NTA5OTBiOGQ0ODBmMzk3YWQzZl8wLmdlb2pzb24iLCB3aGF0ID0gInNwIikNCmhlYWQoa3NpX3BlZCkNCmBgYA0KDQojIyMjIEtTSSBUVEMvTXVuaWNpcGFsIFZlaGljbGUgRGF0YXNldA0KDQpUaGlzIGRhdGFzZXQgaXMgYSBzdWJzZXQgb2YgdGhlIEtpbGxlZCBhbmQgU2VyaW91c2x5IEluanVyZWQgKEtTSSkgZGF0YXNldCBjb2xsZWN0ZWQgYnkgdGhlIFRvcm9udG8gUG9saWNlIFNlcnZpY2UgZnJvbSAyMDA4LTIwMTguIFRoZXNlIGV2ZW50cyBpbmNsdWRlIGFueSBzZXJpb3VzIG9yIGZhdGFsIGNvbGxpc2lvbiBpbnZvbHZpbmcgYW4gb3BlcmF0b3Igb3IgcGFzc2VuZ2VyIG9mIGEgVFRDLCBUcmFuc2l0IFZlaGljbGUsIHN0cmVldGNhciBvciBNdW5pY2lwYWwgVmVoaWNsZS4gVG8gbGVhcm4gbW9yZSBhYm91dCBUVEMtTXVuaWNpcGFsIFZlaGljbGUgcmVsYXRlZCBjb2xsaXNpb25zIGluIFRvcm9udG8geW91IGNhbiBmb2xsb3cgdGhpcyBsaW5rOiBodHRwOi8vZGF0YS50b3JvbnRvcG9saWNlLm9uLmNhL3BhZ2VzL3R0Yy1tdW5pY2lwYWwtdmVoaWNsZQ0KDQpXZSB3aWxsIHVzZSB0aGUgRlJPTV9HZW9Kc29uIGNvbW1hbmQgZnJvbSB0aGUgImdlb2pzb25SIiBwYWNrYWdlIHRvIGRvd25sb2FkIGEganNvbiBmaWxlIGZyb20gdGhlIHByb3ZpZGVkIFVSTC4gVGhlIGdlb2pzb25fcmVhZCBjb21tYW5kIGZyb20gdGhlICJnZW9qc29uaW8iIGFuZCBmb3JtYXRpbmcgZm9ybSB0aGUgInNwIiBwYWNrYWdlIGNhbiB0aGVuIGJlIHVzZWQgdG8gcmVhZCB0aGUganNvbiBhbmQgZ2VuZXJhdGUgYSBTcGF0aWFsUG9pbnRzRGF0YUZyYW1lIA0KYGBge3J9DQprc2lfdHRjIDwtIGdlb2pzb25pbzo6Z2VvanNvbl9yZWFkKCJodHRwczovL29wZW5kYXRhLmFyY2dpcy5jb20vZGF0YXNldHMvZGM0NzUxMjc4ZTYwNGQ2NWIwODg2Yjk3NjVkNGI1NTFfMC5nZW9qc29uIiwgd2hhdCA9ICJzcCIpDQpoZWFkKGtzaV90dGMpDQpgYGANCg0KIyMjIyBNZXJnZSBLU0lfVFRDIGFuZCBLU0lfUGVkDQoNCkFzIHlvdSB3aWxsIGhhdmUgbm90aWNlZCBmcm9tIHRoZSBkZXNjcml0cGlvbnMgYm90aCB0aGUgS1NJX1BFRCBhbmQgS1NJX1RUQyBkYXRhc2V0cyBhcmUgdGhlbXNlbHZlcyBzdWJzZXRzIG9mIHRoZSBUb3JvbnRvIFBvbGljZSBTZXJ2aWNlcycgS1NJIERhdGFzZXQuIFdlIGRvd25sb2FkZWQgdGhlbSBhcyBzZXBlcmF0ZSBkYXRhZnJhbWVzIHRvIGVuYWJsZSBmYXN0ZXIgZG93bmxvYWRzIGFzIHRoZXkgbWFrZSB1cCBhIHNtYWxsIHBvcnRpb24gb2YgdGhlIGZ1bGwgS1NJIGRhdGFzZXQsIHdoaWNoIHdvdWxkIHRha2Ugc2lnbmlmaWNhbnRseSBtb3JlIHRpbWUgdG8gZG93bmxvYWQgYW5kIHNvcnQuDQpCZWNhdXNlIHRoZXkgY29tZSBmcm9tIHRoZSBzYW1lIGJhc2UgZGF0YXNldCBhbmQgaGF2ZSB0aGUgc2FtZSBzY2hlbWEgd2UgY2FuIG1lcmdlIHRoZW0gYmFjayBpbnRvIG9uZSBkYXRhZnJhbWUgdG8gd29yayB3aXRoLiBXZSB3aWxsIG1lcmdlIHRoZW0gYmFzZWQgb24gdGhlICJJbmRleF8iIHRvIGVuc3VyZSB0aGVyZSBhcmUgbm8gZHVwbGljYXRlcyBjcmVhdGVkIGR1ZSB0byB0aGUgbWVyZ2luZyBwcm9jZXNzLg0KYGBge3J9DQprc2lfbWVyZ2VkIDwtIG1lcmdlKGtzaV9wZWQsa3NpX3R0YywgYnk9IkluZGV4XyIpDQpgYGANCg0KDQojIyMjIE5laWdoYm91cmhvb2QgQm91bmRhcmllcyAtIENpdHkgb2YgVG9yb250bw0KDQpUaGUgQ2l0eSBvZiBUb3JvbnRvIGFsc28gbWFpbnRhaW5zIGEgbGlzdCB0aGF0IGRlZmluZXMgdGhlIGJvdW5kYXJpZXMgb2YgYWxsIHRoZSBuZWlnaGJvdXJob29kcyBpbiB0aGUgY2l0eS4gQSBmaWxlIGNvbnRhaW5pbmcgdGhlIHNwYXRpYWwgZGF0YSByZXF1aXJlZCB0byBtYXAgdGhlc2UgbmVpZ2hib3VyaG9vZHMgY2FuIGJlIGRvd25sb2FkZWQgZm9ybSB0aGUgQ2l0eSBvZiBUb3JvbnRvIG9wZW4gZGF0YSBwb3J0YWwuIER1ZSB0byBob3cgdGhpcyBmaWxlIHdpbGwgZG93bmxvYWQsIHVubGlrZSB0aGUgS1NJIGRhdGEsIHdlIGNhbm5vdCBkaXJlY3RseSByZWFkIHRoZSBHZW9Kc29uIGZpbGUgYnV0IGhhdmUgdG8gZG93bmxvYWQgaXQgYmVmb3JlIHRoZSBmaWxlIGNhbiBiZSByZWFkLg0KYGBge3IgZWNobz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnNldF93ZCA8LSBmdW5jdGlvbigpIHsNCiAgbGlicmFyeShyc3R1ZGlvYXBpKSAjIG1ha2Ugc3VyZSB5b3UgaGF2ZSBpdCBpbnN0YWxsZWQNCmN1cnJlbnRfcGF0aCA8LSBnZXRBY3RpdmVEb2N1bWVudENvbnRleHQoKSRwYXRoIA0Kc2V0d2QoZGlybmFtZShjdXJyZW50X3BhdGgpKQ0KcHJpbnQoIGdldHdkKCkgKQ0KfQ0KDQpkb3dubG9hZC5maWxlKCJodHRwczovL2NrYW4wLmNmLm9wZW5kYXRhLmludGVyLnByb2QtdG9yb250by5jYS9kb3dubG9hZF9yZXNvdXJjZS9hMDgzYzg2NS02ZDYwLTRkMWQtYjZjNi1iMGM4YTg1ZjljMTU/Zm9ybWF0PWdlb2pzb24mcHJvamVjdGlvbj00MzI2IiwgZGVzdGZpbGUgPSAiZGF0YXNldHMvTmVpZ2hib3VyaG9vZHMuZ2VvanNvbiIsICkNCg0KbmJoIDwtIGdlb2pzb25pbzo6Z2VvanNvbl9yZWFkKCJkYXRhc2V0cy9OZWlnaGJvdXJob29kcy5nZW9qc29uIiwgd2hhdCA9ICJzcCIpDQoNCmhlYWQobmJoKQ0KYGBgDQoNCiMjIyMgU2ltcGx5IEFuYWx5dGljcyAtIENlbnN1cyBEYXRhDQoNCkZyb20gU2ltcGx5IEFuYWx5dGljcyBpIGhhdmUgZG93bmxvYWRlZCBzaGFwZWZpbGVzIGNvbnRhaW5pbmcgYXZlcmFnZSBpbmNvbWUgYnkgIGNlbnN1cyB0cmFjayBhbmQgc3ViZGl2aXNpb24uIFRoZXNlIHR3byBzaGFwZWZpbGVzIHdpbGwgdXNlZnVsbCBpbiBkcmlsbGluZyBkb3duIGJleW9uZCB0aGUgbmVpZ2hib3VyaG9vZC4NCkJvdGggc2hhcGVmaWxlcyBjYW4gYmUgdXBsb2FkZWQgaW50byBhIHNwYXRpYWwgZGF0YWZyYW1lIHVzaW5nIHRoZSBzdF9yZWFkIGNvbW1hbmQgZnJvbSB0aGUgInNmIiBwYWNrYWdlIHdlIGluc3RhbGxlZCBlYXJsaWVyIHRoZW4gY29udmVydGluZyB0aGVtIHRvIGEgU3BhdGlhbFBvbHlnb25zRGF0YUZyYW1lLg0KDQpgYGB7cn0NCm9nckluZm8oZHNuPSJkYXRhc2V0cy9TaW1wbHlBbmFseXRpY3NfQzEiLCBsYXllcj0iQzEiKQ0KY3RyIDwtIHJlYWRPR1IoZHNuID0gImRhdGFzZXRzL1NpbXBseUFuYWx5dGljc19DMSIsIGxheWVyID0gIkMxIikNCm5hbWVzKGN0ckBkYXRhKVtuYW1lcyhjdHJAZGF0YSk9PSJWQUxVRTAiXSA8LSAiSG91c2Vob2xkIFRvdGFsIEluY29tZSBBZnRlci1UYXgiDQpuYW1lcyhjdHJAZGF0YSlbbmFtZXMoY3RyQGRhdGEpPT0iVkFMVUUxIl0gPC0gIkhvdXNlaG9sZCBBZ2dyZWdhdGUgSW5jb21lIg0KbmFtZXMoY3RyQGRhdGEpW25hbWVzKGN0ckBkYXRhKT09IlZBTFVFMiJdIDwtICJIb3VzZWhvbGQgQXZlcmFnZSBJbmNvbWUiDQpoZWFkKGN0cikNCmBgYA0KDQpgYGB7cn0NCm9nckluZm8oZHNuPSJkYXRhc2V0cy9TaW1wbHlBbmFseXRpY3NfQzIiLCBsYXllcj0iQzIiKQ0KZGlzZW0gPC0gcmVhZE9HUihkc24gPSAiZGF0YXNldHMvU2ltcGx5QW5hbHl0aWNzX0MyIiwgbGF5ZXIgPSAiQzIiKQ0KbmFtZXMoZGlzZW1AZGF0YSlbbmFtZXMoZGlzZW1AZGF0YSk9PSJWQUxVRTAiXSA8LSAiSG91c2Vob2xkIFRvdGFsIEluY29tZSBBZnRlci1UYXgiDQpuYW1lcyhkaXNlbUBkYXRhKVtuYW1lcyhkaXNlbUBkYXRhKT09IlZBTFVFMSJdIDwtICJIb3VzZWhvbGQgQWdncmVnYXRlIEluY29tZSINCm5hbWVzKGRpc2VtQGRhdGEpW25hbWVzKGRpc2VtQGRhdGEpPT0iVkFMVUUyIl0gPC0gIkhvdXNlaG9sZCBBdmVyYWdlIEluY29tZSINCnN1bW1hcnkoZGlzZW0pDQpoZWFkKGRpc2VtQGRhdGEpDQpgYGANCg0KIyMjIyBBZGRpdGlvbmFsIERhdGF0YWJsZXMNCg0KT25lIGFkZGl0aW9uYWwgZGF0YXRhYmxlIG1heSBiZSBuZWVkZWQgZGVwZW5kaW5nIG9uIGhvdyB3ZSBjaG9vc2UgdG8gY2x1c3RlciB0aGUgS1NJIGRhdGEuDQpBdCBhIGhpZ2ggbGV2ZWwgZ3JvdXBpbmcgYWNjaWRlbnRzIHRvIGEgZ2l2ZW4gbmVpZ2hib3VyaG9vZCBhcyBkZWZpbmVkIGJ5IHRoZSBjaXR5IHRoZW4gdXNpbmcgdGhlIGNlbnN1cyBkYXRhIHRvIGZ1cnRoZXIgY2x1c3RlciBieSBjZW5zdXMgdHJhY2sgYW5kIHN1YmRpdmlzaW9uIHdpbGwgYmUgaWRlYWwuIEkgbWF5IG5lZWQgdG8gcHVsbCB0aGUgTmVpZ2hib3VyaG9vZCBQcm9maWxlcyBkYXRhIHNldCBmcm9tIHRoZSBjaXR5IG9mIFRvcm9udG8gdG8gZ2V0IGFuIGF2ZXJhZ2UgaW5jb21lIGxldmVsIGJ5IG5laWdoYm91cmhvb2QgYXMgdGhpcyBkb2VzIG5vdCBhcHBlYXIgdG8gYmUgYXZhaWFsYmxlIGRpcmVjdGx5IGZyb20gdGhlIGNlbnN1cyBkYXRhc2V0cy4NCg0KDQojIyBSZXZpZXdpbmcgdGhlIERhdGFzZXRzDQoNCldpdGggb3VyIGRhdGFzZXRzIGRvd25sb2FkZWQgd2UgY2FuIHN0YXJ0IGJ5IGxvb2tpbmcgdGhyb3VnaCB0aGUgZGF0YS4gQmVjYXN1ZSB3ZSBhcmUgZGVhbGluZyBhbG1vc3QgZXhjbHVzaXZlbHkgd2l0aCBzcGF0aWFsIGRhdGEgdGhlIGVhc2llc3Qgd2F5IHRvIGdldCBhIGhhbmRsZSBvbiB0aGUgZGF0YSBpcyBieSBtYXBwaW5nIGl0Lg0KVG8gZG8gdGhpcyBJIHdpbGwgYmUgdXNpbmcgZmVhdHVyZXMgZnJvbSB0aGUgImxlYWZsZXQiIHBhY2thZ2UuDQoNCiMjIyMgS1NJX21lcmdlZCBEYXRhc2V0DQoNCkxldHMgc2F0cnQgYnkgZ2V0dGluZyBhbiBpZGVhIG9mIHdoZXJlIGFjY2lkZW50cyBhcmUgb2NjdXJpbmcuIFRoZSBsZWFmbGV0IHBhY2thZ2Ugd2lsbCBtYXAgdGhlIGRhdGEgZm9yIHVzLiBUbyBtYWtlIHRoaXMgbW9yZSByZWFkYWJsZSBJIGhhdmUgaGFkIHRoZSBzeXN0ZW0gYXV0b2NsdXN0ZXIgdGhlIGFjY2lkZW50cy4gVGhlc2UgY2x1c3RlcnMgZG9uJ3QgaGF2ZSBhbnkgcmVsYXRpb24gdG8gc3BlY2lmaWMgbmVpZ2hib3VyaG9vZHMgYW5kIHdpbGwgbmVlZCB0byBiZSBhZGp1c3RlZCBsYXRlciBzbyB0aGF0IHRoZSBjbHVzdGVycyBhcmUgaW4gbGluZSB3aXRoIG91ciBvdGhlciBkYXRhc2V0cy4NCmBgYHtyfQ0KbGVhZmxldChrc2lfbWVyZ2VkKSAlPiUNCiAgYWRkVGlsZXMoKSAlPiUNCiAgYWRkTWFya2VycyhsbmcgPSBrc2lfbWVyZ2VkJExPTkdJVFVERSwgbGF0ID0ga3NpX21lcmdlZCRMQVRJVFVERSwgY2x1c3Rlck9wdGlvbnMgPSBtYXJrZXJDbHVzdGVyT3B0aW9ucygpKQ0KYGBgDQoNClRoaXMgbWFwIHdpbGwgYWxsb3cgeW91IHRvIHpvb20gaW4gYW5kIHRoZSBjbHVzdGVycyB3aWxsIGF1dG8gYWRqdXN0IGFzIHlvdSB6b29tIGluIGFuZCBvdXQuIFRoZXNlIGNsdXN0ZXJzIGFyZSBiYXNlZCBvbiBwcm94aW1pdHkgdG8gYSBjZW50cmFsIHBvaW50LiBPbmNlIHlvdSBnZXQgdG8gdGhlIGxvd2VzdCB6b29tIGxldmVscywgY2xpY2tpbmcgb24gYSBjbHVzdGVyIHdpbGwgbWFwIHRoZSBpbmRpdmlkdWFsIGFjY2lkZW50cy4gRGV0YWlscyBmb3IgZWFjaCBhY2NpZGVudCBhcmUgbm90IGN1cnJlbmx0eSBpbmNsdWRlZCBpbiB0aGUgbWFwcGluZy4NCg0KDQojIyMjIE5laWdoYm91cmhvb2QgQm91bmRhcmllcyAtIENpdHkgb2YgVG9yb250bw0KDQpPdXIgbmV4dCBkYXRhc2V0IGNvbnRhaW5zIHRoZSBib3VuZGFyeSBsaW5lcyBmb3IgdmFyaW91cyBuZWlnaGJvdXJob29kcyBpbiBUb3JvbnRvLiBtYXBwaW5nIHRoaXMgd2lsbCBiZWdpbiB0byBnaXZlIHNvbWUgZGltZW5zaW9uIHRvIGhvdyB3ZSBpbnRlbmQgdG8gY2x1c3RlciBhY2NpZGVudHMgZ29pbmcgZm9yd2FyZC4NCmBgYHtyfQ0KbGVhZmxldChuYmgpICU+JQ0KICBhZGRUaWxlcygpICU+JQ0KICBhZGRQb2x5Z29ucygpDQpgYGANCg0KIyMjIyBTaW1wbHkgQW5hbHl0aWNzIC0gQ2Vuc3VzIERhdGENCg0KV2UgY2FuIHNpbWlsYXJseSBwbG90IGJvdGggdGhlIGNlbnN1cyB0cmFjdCBhbmQgZGlzc2VtaW5hdGlvbiBhcmVhIGZpbGVzIHB1bGxlZCBmcm9tIFNpbXBseSBBbmFseXRpY3MuDQpgYGB7cn0NCmxlYWZsZXQoY3RyKSAlPiUNCiAgYWRkVGlsZXMoKSAlPiUNCiAgYWRkUG9seWdvbnMoKQ0KYGBgDQoNCmBgYHtyfQ0KbGVhZmxldChkaXNlbSkgJT4lDQogIGFkZFRpbGVzKCkgJT4lDQogIGFkZFBvbHlnb25zKCkNCmBgYA0KDQpPdXIgbmV4dCBzdGVwIHdpbGwgYmUgdG8gYXNzaW5nIGVhY2ggYWNjaWRlbnQgaW4gdGhlIEtTSSBkYXRhIHRvIGEgZGlzc2VtaW5hdGlvbiBhcmVhIHBvbHlnb24uIEJ1dCBiZWZvcmUgd2UgY2FuIGRvIHRoYXQgd2Ugd2lsbCBmaXJzdCBoYXZlIHRvIGNsZWFuIHVwIHRoZSB2YXJpb3VzIGRhdGFzZXRzLg0KDQoNCiMjIENsZWFuaW5nIHRoZSBEYXRhDQoNCkJlZm9yZSB3ZSBiZWdpbiB3ZSBuZWVkIHRvIGVuc3VyZSB0aGF0IG91ciB2YXJpb3VzIGRhdGFzZXRzIGFyZSBjb21wYXRpYmxlLg0KDQpVc2luZyB0aGUgQ1JTIGZ1Y3Rpb24gaW4gdGhlIFNGIHBhY2thZ2UgV2UgY2FuIGNoZWNrIHRoZSBjb29yZGluYXRlIHJlZmVyZW5jZSBzeXN0ZW0gYmVpbmcgdXNlZCBieSBvdXIgbWFpbiBmaWxlcyBhbmQgY2FuIHNlZSB0aGF0IHRoZXkgYXJlIGFsbCB0aGUgc2FtZS4NCmBgYHtyfQ0Kc3RfY3JzKGtzaV9tZXJnZWQpDQpzdF9jcnMoY3RyKQ0Kc3RfY3JzKGRpc2VtKQ0Kc3RfY3JzKG5iaCkNCmBgYA0KDQpXZSBsdWNraWx5IGFsbCB0aGUgZmlsZXMgd2UgaGF2ZSBkb3dubG9hZGVkIHVzZSB0aGUgc2FtZSBjby1vcmRpbmF0ZSByZWZlcmVuY2Ugc3lzdGVtIHNvIHdlIHdpbGwgYmUgYWJsZSB0byBjb21wYXJlIGFuZCBhc3NvY2lhdGUgdGhlbSB0byBvbmUgYW5vdGhlciB3aXRob3V0IGhhdmluZyB0byByZS1wcm9qZWN0IHRoZW0gaW50byBhIGNvbW9uIGNvLW9yZGluYXRlIHN5c3RlbS4gVGhpcyBpcyBlc3BlY2lhbGx5IGNvbnZlbmllbnQgZ2l2ZW4gdGhhdCB0aGUgbWFwcGluZyBwYWNrYWdlIHcgYXJlIHVzaW5nICJsZWFmbGV0IiBkb2VzIG5vdCBzdXBwb3J0ICJDUlMiIG9uZSBvZiB0aGUgb3RoZXIgcmVmZXJlbmNlIHN5c3RlbXMuDQoNCiMjIyBDbGVhbmluZyB0aGUgS1NJIGRhdGENCg0KQXMgd2UgcmV2aWV3IHRoZSBkYXRhc2V0cyBvbmUgdGhpbmcgeW91IG1heSBoYXZlIG5vdGljZWQgaXMgdGhhdCB0aGUgS1NJIGRhdGFzZXQgY29udGFpbnMgYSBsYXJnZSBudW1iZXIgb2YgcG90ZW5pdGFsbHkgZHVwbGljYXRlIGVudHJpZXMuDQoNCmBgYHtyfQ0KaGVhZChrc2lfbWVyZ2VkQGRhdGEsIDIwKQ0KYGBgDQoNCldoaWxlIGVhY2ggYWNjaWRlbnQgaXMgYXNzaWduZWQgYSBkaWZmZXJlbnQgaW5kZXggaWQgbnVtYmVyKEluZGV4XyksIGluIG1vc3QgY2FzZXMgbXVsdGlwbGUgaW5kZXggbnVtYmVycyBhcmUgYXNzaWduZWQgdG8gdGhlIHNhbWUgYWNjb3VudCBudW1iZXIoQUNDTlVNKS4gVGhpcyBpcyBiZWNhdXNlLCBmb3IgZWFjaCBhY2NpZGVudCwgdGhlIGRyaXZlciBvZiB0aGUgdmVoaWNsZSwgdGhlIHBlZGVzdHJpYW4sIGFuZCBhbnkgb3RoZXIgYXBwbGljYWJsZSBpbmRpdmlkdWFsIHJlbGF0ZWQgdG8gdGhlIGxvc3MgaGFzIHRoZWlyIGluZm9ybWF0aW9uIHJlY29yZGVkIHRvIHRoZSBkYXRhc2V0LiBUaGlzIGlzIG1vc3QgZXZpZGVudCBpZiB5b3UgbG9vayBhdCB0aGUgaW52b2x2ZWQgdmVoaWNsZSB0eXBlIChJTlZUWVBFKSBjb2x1bW5zLg0KDQpTaW5jZSB0aGlzIHJldmlldyBpcyBmb2N1c2VkIG9uIHBlZGVzdHJpYW4gYWNjaWRlbnRzLCB3ZSB3aWxsIGZpbHRlciBvdXIgS1NJX01lcmdlZCBmaWxlIHRvIGluY2x1ZGUgb25seSBpbnN0YW5jZXMgd2hlcmUgdGhlIGNvbHVtbiBJTlZUWVBFIGlzIGEgcGVkZXN0cmlhbi4NCg0KV2UgY2FuIGVhc2lseSBpZGVudGlmeSBhbGwgdW5pcXVlIGl0ZW1zIGluIHRoZSBJTlZUWVBFIGNvbHVtbiBhbmQgc2VsZWN0IHRob3NlIHRoYXQgYXJlIHJlcHJlc2VudGF0aXZlIG9mIHBlZGVzdHJpYW5zLg0KDQpgYGB7cn0NCnVuaXF1ZShrc2lfbWVyZ2VkQGRhdGEkSU5WVFlQRSkNCmBgYA0KDQpJZiB3ZSBsb29rIGF0IG91ciBsaXN0IG9mIG9wdGlvbnMsIHRoZXJlIGFyZSBhIGZldyBkaWZmZXJlbnQgZW50cmllcyB3ZSBzaG91bGQgYmUgY29uc2lkZXJpbmcgdG8gYmUgYSBwZWRlc3RyaWFuLg0KRm9yIHRoaXMgcmV2aWV3IHdlIHdpbGwgYmUgZmlsdGVyaW5nIG91dCBhbGwgZW50cmllcyB3aGVyZSB0aGUgSU5WVFlQRSBpcyAiUGVkZXN0cmlhbiIsICJQZWRlc3RyaWFuIC0gTm90IEhpdCIsICJJbi1MaW5lIFNrYXRlciIsIG9yICJXaGVlbGNoYWlyIi4NCg0KU28gYXMgbm90IHRvIGxvc2Ugb3VyIG1lcmdlZCBkYXRhc2V0LCB3ZSB3aWxsIGFsc28gY3JlYXRlIGEgbmV3IGZpbHRlcmVkIGRhdGF0YWJsZS4NCldlIGNhbiBlYXNpbHkgaWRlbnRpZnkgYW5kLCBpZiByZXF1aXJlZCBpbiB0aGUgZnV0dXJlLCBtb2RpZnkgdGhlIGxpc3Qgb2YgaXRlbXMgYmVpbmcgZmlsdGVyZWQgaW50byBvdXIgZGF0YXRhYmxlIGJ5IHNldHRpbmcgdGhlbSBhcyBhIHRhcmdldA0KYGBge3J9DQp0YXJnZXRfaW52dHlwZSA8LSBjKCJQZWRlc3RyaWFuIiwgIlBlZGVzdHJpYW4gLSBOb3QgSGl0IiwgIkluLUxpbmUgU2thdGVyIiwgIldoZWVsY2hhaXIiKQ0Ka3NpX21vZGlmaWVkIDwtIGtzaV9tZXJnZWRba3NpX21lcmdlZEBkYXRhJElOVlRZUEUgJWluJSB0YXJnZXRfaW52dHlwZSxdDQpoZWFkKGtzaV9tb2RpZmllZEBkYXRhLCAyMCkNCmBgYA0KDQpUaGVyZSBhcmUgYWxzbyBhIG51bWJlciBvZiBjb2x1bW5zIHRoYXQgd2lsbCBub3QgYmUgbmVlZGVkIGZvciBvdXIgcmV2aWV3IGFuZCBjYW4gYmUgcmVtb3ZlZCBmcm9tIG91ciBtb2RpZmllZCBkYXRhc2V0Lg0KVGhlc2UgaW5jbHVkZTogT0ZGU0VULCBWRUhUWVBFLCBNQU5PRVVWRVIsIERSSVZBQ1QsIERSSVZDT05ELCBDWUNMSVNUWVBFLCBDWUNBQ1QsIENZQ0NPTkQsIENZQ0xJU1QsIFRSU05fQ0lUWV9WRUgsIFRSU05fQ0lUWV8sIGNvb3Jkcy54MSwgY29vcmRzLngyDQoNCmBgYHtyfQ0KdGFyZ2V0X2NvbGwgPC0gYygiT0ZGU0VUIiwgIlZFSFRZUEUiLCAiTUFOT0VVVkVSIiwgIkRSSVZBQ1QiLCAiRFJJVkNPTkQiLCAiQ1lDTElTVFlQRSIsICJDWUNBQ1QiLCAiQ1lDQ09ORCIsICJDWUNMSVNUIiwgIlRSU05fQ0lUWV9WRUgiLCAiVFJTTl9DSVRZXyIsICJjb29yZHMueDEiLCAiY29vcmRzLngyIikNCmtzaV9tb2RpZmllZCA8LSBrc2lfbW9kaWZpZWRbLC13aGljaChuYW1lcyhrc2lfbW9kaWZpZWRAZGF0YSkgJWluJSBjKHRhcmdldF9jb2xsKSldDQpoZWFkKGtzaV9tb2RpZmllZEBkYXRhLCAyMCkNCmBgYA0KDQojIyMgQ2xlYW5pbmcgdGhlIFRvcm9udG8gTmVpZ2hib3VyaG9vZCBkYXRhDQoNCk5leHQgd2Ugd2lsbCBsb29rIGF0IHRoZSBUb3JvbnRvIE5laWdoYm91cmhvb2QgZGF0YXNldC4NCg0KYGBge3J9DQpoZWFkKG5iaEBkYXRhLCAyMCkNCmBgYA0KDQpXZSBjYW4gc2VlIHRoYXQgZm9yIHRoZSBtb3N0IHBhcnQgYWxsIGNvbHVtbnMgaW4gdGhpcyBkYXRhIHNldCBhcmUgdXNlZnVsLiBUaGUgY29sdW1ucyB0aGF0IHdpbGwgbm90IGJlIG9mIHVzZSBhbmQgY2FuIGJlIHJlbW92ZWQgYXJlOg0KICBQQVJFTlRfQVJFQV9JRCAtIHRoaXMgZmllbGQgY29udGFpbmUgbm8gdW5pcXVlIHZhbHVlcw0KICBBUkVBX0xPTkdfQ09ERSAtIHRoaXMgaXMgdmFyaWFibGUgaXMgaWRlbnRpY2FsIHRvIEFSRUFfU0hPUlRfQ09ERQ0KICBBUkVBX0RFU0MgLSB0aGlzIGlzIHZhcmlhYmxlIGlzIGlkZW50aWNhbCB0byBBUkVBX05BTUUNCiAgWCAtIHRoaXMgZmllbGQgY29udGFpbmUgbm8gdW5pcXVlIHZhbHVlcw0KICBZIC0gdGhpcyBmaWVsZCBjb250YWluZSBubyB1bmlxdWUgdmFsdWVzDQoNCmBgYHtyfQ0KdW5pcXVlKG5iaEBkYXRhJFBBUkVOVF9BUkVBX0lEKQ0KdW5pcXVlKG5iaEBkYXRhJFgpDQp1bmlxdWUobmJoQGRhdGEkWSkNCmBgYA0KDQpgYGB7cn0NCnRhcmdldG5iaCA8LSBjKCJQQVJFTlRfQVJFQV9JRCIsICJBUkVBX0xPTkdfQ09ERSIsICJBUkVBX0RFU0MiLCAiWCIsICJZIikNCm5iaF9tb2RpZmllZCA8LSBuYmgNCm5iaF9tb2RpZmllZCA8LSBuYmhfbW9kaWZpZWRbLC13aGljaChuYW1lcyhuYmhfbW9kaWZpZWRAZGF0YSkgJWluJSBjKHRhcmdldG5iaCkpXQ0KaGVhZChuYmhfbW9kaWZpZWRAZGF0YSwgMjApDQpgYGANCg0KIyMjIENsZWFuaW5nIHRoZSBDZW5zdXMgVHJhY3QgYW5kIERpc2VtaW5hdGlvbiBkYXRhDQoNCk5leHQgd2Ugd2lsbCBsb29rIGF0IHRoZSBUb3JvbnRvIE5laWdoYm91cmhvb2QgZGF0YXNldC4NCg0KYGBge3J9DQpoZWFkKGN0ckBkYXRhLCAyMCkNCmBgYA0KDQpXZSBjYW4gc2VlIHRoYXQgdGhlc2UgaGF2ZSBhIHZlcnkgbWluaW1hbCBudW1iZXIgb2YgZmllbGRzIGFuZCB3aWxsIG5vdCByZXF1aXJlIGFueSBhZGp1c3RtZWVudHMgcHJpb3IgdG8gcHJvY2VlZGluZyB3aXRoIG91ciByZXZpZXcuDQoNCg0KIyMgQXNzb2NpYXRpbmcgdGhlIERhdGFzZXRzDQoNCldlIGFyZSBpbnRlcmVzdGVkIGluIGFzc29jaWF0aW5nIGFsbCBvdXIgc3BhdGlhbCBwb2x5Z29uIGRhdGFmcmFtZXMgdG8gdGhlIEtTSSBkYXRhIHBvaW50cyB0aGF0IHJlcHJlc2VudCBlYWNoIHBlZGVzdHJpYW4gcmVsYXRlZCBhY2NpZGVudC4NCg0KT3VyIGZpcnN0IHRhc2sgd2lsbCBiZSB0byBqb2luIG91ciBLU0kgZGF0YXNldCB0byBhIFRvcm9udG8gTmVpZ2hib3VyaG9vZC4NCg0KVGhpcyBjYW4gYmUgZG9uZSB1c2luZyB0aGUgc3BhdGlhbEVjbyBwYWNrYWdlLiBUaGUgc3BhdGlhbEVjbzo6cG9pbnQuaW4ucG9seSBmdW5jdGlvbiBpbnRlcnNlY3RzIHBvaW50IGFuZCBwb2x5Z29uIGZlYXR1cmUgY2xhc3NlcyBhbmQgYWRkcyBwb2x5Z29uIGF0dHJpYnV0ZXMgdG8gcG9pbnRzLiBUaGlzIGZ1bmN0aW9uIHdpbGwgcmUtbmFtZSBjb2x1bW5zIHdpdGggc2ltaWxhciBuYW1lcyBzbyB3ZSB3aWxsIGFsc28gYWRkcmVzcyByZS1uYW1pbmcgc29tZSBvZiB0aGUgbmV3bHkgYWRkZWQgY29sdW1ucy4NCg0KYGBge3J9DQprc2lfY29vcmQgPC0gc3BhdGlhbEVjbzo6cG9pbnQuaW4ucG9seShrc2lfbW9kaWZpZWQsIG5iaF9tb2RpZmllZCkNCm5hbWVzKGtzaV9jb29yZEBkYXRhKVtuYW1lcyhrc2lfY29vcmRAZGF0YSk9PSJMT05HSVRVREUueCJdIDwtICJMT05HSVRVREUiDQpuYW1lcyhrc2lfY29vcmRAZGF0YSlbbmFtZXMoa3NpX2Nvb3JkQGRhdGEpPT0iTEFUSVRVREUueCJdIDwtICJMQVRJVFVERSINCm5hbWVzKGtzaV9jb29yZEBkYXRhKVtuYW1lcyhrc2lfY29vcmRAZGF0YSk9PSJMT05HSVRVREUueSJdIDwtICJMT05HSVRVREUubmJoIg0KbmFtZXMoa3NpX2Nvb3JkQGRhdGEpW25hbWVzKGtzaV9jb29yZEBkYXRhKT09IkxBVElUVURFLnkiXSA8LSAiTEFUSVRVREUubmJoIg0KaGVhZChrc2lfY29vcmRAZGF0YSwgMjApDQpgYGANCg0KTmV4dCB3ZSB3aWxsIGFkZCBpbiB0aGUgZGlzZW1pbmF0aW9uIGFyZWEgaW5mb3JtYXRpb24uIEJlaW5nIHRoZSBtb3N0IGdyYW51bGFyIHRoaXMgd2lsbCBhbGxvdyB1cyB0byBhY2N1cmF0ZWx5IGFzc29jaWF0ZSBhbiBhdmVyYWdlIGluY29tZSB0byBlYWNoIGFjY2lkZW50Lg0KDQpgYGB7ciB3YXJuaW5nPUZBTFNFfQ0Ka3NpX2Nvb3JkIDwtIHNwYXRpYWxFY286OnBvaW50LmluLnBvbHkoa3NpX2Nvb3JkLCBkaXNlbSkNCmhlYWQoa3NpX2Nvb3JkQGRhdGEsIDIwKQ0KYGBgDQoNCk5vdyB0aGF0IHdlIGhhdmUgYSBhc3NpZ25lZCBhbiBhdmVyYWdlIGluY29tZSB0byBlYWNoIGFjY2lkZW50IHdlIGNhbiBzdGFydCB3b3JraW5nIHRvIHJldmlldyBvdXIgZGF0YSBhbmQgbGVhcm4gbW9yZSBhYm90IHdoYXQgc3RvcnkgaXQgY2FuIHRlbGwuDQoNClNpbmNlIHdlIGFyZSB0cnlpbmcgdG8gbG9vayBhdCBzb2Npb2Vjb25vbWl2IGltcGFjdCBvbiBhY2NpZGVudHMgd2Ugd2lsbCBmaXJzdCBzdGFydCBieSBmb2N1c2luZyBvbiBhdmVyYWdlIGluY29tZS4NCg0KTGV0cyBzdGFydCBieSBjcmVhdGluZyBhIGhpc3RvZ3JhbSBvZiB0aGUgSG91c2Vob2xkIEF2ZXJhZ2UgSW5jb21lIGluZm9ybWF0aW9uIGZvciBvdXIgYWNjaWRlbnRzLg0KDQpgYGB7cn0NCnFwbG90KGtzaV9jb29yZCRIb3VzZWhvbGQuQXZlcmFnZS5JbmNvbWUsDQogICAgICBnZW9tPSJoaXN0b2dyYW0iLA0KICAgICAgYmlud2lkdGggPSA1LA0KICAgICAgbWFpbiA9ICJIaXN0b2dyYW0gQXZlcmFnZSBJbmNvbWUiLA0KICAgICAgeGxhYiA9ICJBdmVyYWdlIEluY29tZSIsDQogICAgICBmaWxsPUkoImJsdWUiKSwgDQogICAgICBjb2w9SSgicmVkIikpDQpgYGANCg0KQXMgd2UgY2FuIHNlZSB0aGUgbWFqb3JpdHkgb2YgbG9zc2VzIGFyZSBjbHVzdGVyZWQgYXQgdGhlIGJvdHRvbSBlbmQgb2YgdGhlIGluY29tZSBzY2FsZS4gSG93ZXZlciB0aGlzIGhpc3RvZ3JhbSBjYW4gYmUgZGVjZWl2aW5nLiBUaGUgYXZlcmFnZSBpbmNvbWUgdmFyaWVzIHNvIGdyZWF0bHkgYmV0d2VlbiB0aGUgbG93ZXN0IGFuZCBoaWdoZXN0IHZhbHVlcyB0aGF0IGl0IGlzIGhhcmQgdG8gaWRlbnRpZnkgd2hhdCBpbmNvbWUgYnJha2V0cyB0aGlzIGNsdXN0ZXIgYWN0dWFsbHkgcmVwcmVzZW50cy4NCg0KV2UgY2FuIHJlLXByZXNlbnQgdGhlIHNhbWUgZ3JhcGggd2l0aCBhIHJlZHVjZWQgYmluIHdpZHRoIGJ1dCBpdCBkb2VzIGxpdHRsZSB0byBtYWtlIHRoZSBkYXRhIHBvaW50cyBtb3JlIG1lYW5pbmdmdWwuDQoNCmBgYHtyfQ0KcXBsb3Qoa3NpX2Nvb3JkJEhvdXNlaG9sZC5BdmVyYWdlLkluY29tZSwNCiAgICAgIGdlb209Imhpc3RvZ3JhbSIsDQogICAgICBtYWluID0gIkhpc3RvZ3JhbSBBdmVyYWdlIEluY29tZSIsDQogICAgICB4bGFiID0gIkF2ZXJhZ2UgSW5jb21lIiwNCiAgICAgIGZpbGw9SSgiYmx1ZSIpLCANCiAgICAgIGNvbD1JKCJyZWQiKSkNCmBgYA0KDQoNClRvIGFsbG93IHVzIHRvIG1vcmUgZWFzaWx5IHJldmlldyB0aGUgZGF0YSBzZXQgd2Ugd2lsbCBhbHNvIGNvbnZlcnQgaXQgZnJvbSBhIFNwYXRpYWxQb2ludHNEYXRhRnJhbWUgdG8gYSByZWd1bGFyIGRhdGFmcmFtZS4gV2UgY2FuIHRoZW4gcHVsbCBzb21lIGJhc2ljIGluZm9ybWF0aW9uIGFib3V0IGhvdXNlaG9sZCBpbmNvbWUuDQoNCmBgYHtyfQ0Ka3NpX3NmIDwtIGFzLmRhdGEuZnJhbWUoa3NpX2Nvb3JkQGRhdGEpDQptZWFuKGtzaV9zZiRIb3VzZWhvbGQuQXZlcmFnZS5JbmNvbWUsIG5hLnJtID0gVFJVRSkNCm1vZGUoa3NpX3NmJEhvdXNlaG9sZC5BdmVyYWdlLkluY29tZSkNCnJhbmdlKGtzaV9zZiRIb3VzZWhvbGQuQXZlcmFnZS5JbmNvbWUsIG5hLnJtID0gVFJVRSkNCnF1YW50aWxlKGtzaV9zZiRIb3VzZWhvbGQuQXZlcmFnZS5JbmNvbWUsIG5hLnJtID0gVFJVRSkNCnF1YW50aWxlKGtzaV9zZiRIb3VzZWhvbGQuQXZlcmFnZS5JbmNvbWUsIG5hLnJtID0gVFJVRSwgcHJvYnMgPSBjKDAuMDEsIDAuMDI1LCAwLjA1LCAwLjEsIDAuMiwgMC4zLCAwLjQsIDAuNSwgMC43NSwgMS4wMCkpDQpgYGANCg0KVGhlIHF1YW50aWxlIHJlc3VsdHMgYXJlIHZlcnkgaW50ZXJlc3RpbmcgaW4gdGhhdCB3ZSBjYW4gc2VlIHRoYXQgdGhlIGJvdHRvbSAyNSUgb2YgaW5jb21lcyBhY3R1YWxseSB2YXJpZXMgYmV0d2VlbiAkMCBhbmQgJDY1LDAwMCBhbmQgdGhlIGhpZ2hlc3QgdmFsdWUgaXMgd2VsbCBiZXlvbmQgYmVpbmcgcGxhdXNpYmx5IGNvbXBhcmVkIHRvIHRoZSByZXN0DQoNClRoaXMgZG9lcyBnaXZlIHNvbWUgaW5kaWNhdGlvbiB0aGF0IHdlIHdpbGwgcHJvYmFibHkgd2FudCB0byBmb2N1cyBvbiB0aGUgYm90dG9tIDUwJSBvZiBpbmNvbWVzIGRlcGVkbmluZyBvbiBob3cgdGhlIGluY29tZSBsZXZlbCBjb3JlbGF0ZXMgdG8gZnJlcXVlbmN5IG9mIGFjY2lkZW50cw0KDQoNCmBgYHtyIHdhcm5pbmc9RkFMU0V9DQprc2lfc2YgPC0gYXMuZGF0YS5mcmFtZShrc2lfY29vcmRAZGF0YSkNCg0KZnJlcSA8LSB0YWJsZShrc2lfc2YkWUVBUiwga3NpX3NmJEFSRUFfTkFNRSwga3NpX3NmJFJPQURfQ0xBU1MsIGtzaV9zZiRIb3VzZWhvbGQuQXZlcmFnZS5JbmNvbWUpDQp5ZWFyZnJlcSA8LSBrc2lfc2YgJT4lDQogIGdyb3VwX2J5KFlFQVIpICU+JQ0KICBzdW1tYXJpc2UoZnJlcSA9IG4oKSkNCnllYXJmcmVxIDwtIGFzLmRhdGEuZnJhbWUoeWVhcmZyZXEpDQpBcmVhTG9zc0ZyZXEgPC0ga3NpX3NmICU+JQ0KICBncm91cF9ieShBUkVBX05BTUUpICU+JQ0KICBzdW1tYXJpc2UoZnJlcSA9IG4oKSkNCkFyZWFMb3NzRnJlcSA8LSBhcy5kYXRhLmZyYW1lKEFyZWFMb3NzRnJlcSkNClJvYWRDbGFzc0ZyZXEgPC0ga3NpX3NmICU+JQ0KICBncm91cF9ieShST0FEX0NMQVNTKSAlPiUNCiAgc3VtbWFyaXNlKGZyZXEgPSBuKCkpDQpSb2FkQ2xhc3NGcmVxIDwtIGFzLmRhdGEuZnJhbWUoUm9hZENsYXNzRnJlcSkNCkF2ZUluY29tZUZyZXEgPC0ga3NpX3NmICU+JQ0KICBncm91cF9ieShIb3VzZWhvbGQuQXZlcmFnZS5JbmNvbWUpICU+JQ0KICBzdW1tYXJpc2UoZnJlcSA9IG4oKSkNCkF2ZUluY29tZUZyZXEgPC0gYXMuZGF0YS5mcmFtZShBdmVJbmNvbWVGcmVxKQ0KYGBgDQoNCkEgc3RhbmRhcmQgZnJlcXVlbmN5IHBsb3Qgd2lsbCBkbyBtdWNoIGJldHRlciBhdCBpbmRpY2F0aW5nIHRoZSBhdmVyYWdlIGluY29tZSBvZiB0aGUgYXJlYXMgd2hlcmUgdGhlc2UgcGVkZXN0cmlhbiBhY2NpZGVudHMgdXN1YWxseSBvY2N1ciBidXQgYWdhaW4gd2lsbCBub3QgcHJvdmlkZSBhbnkgcmVsZXZhbnQgZGF0YQ0KYGBge3J9DQpwbG90KEF2ZUluY29tZUZyZXEpDQpgYGANCg0KV2Ugd2lsbCB3YW50IHRvIHVzZSB0aGUgcXVhbnRpbGUgcmVzdWx0cyB0byBzZXQgaW5jb21lIGJhbmR3aXRocyB0byBhcHBseSB0byBlYWNoIG9mIG91ciBhY2NpZGVudHMuIEFzIGV2aWRlbnQgZnJvbSB0aGUgaGl0b2dyYW1zIGFuZCBmcmVxdWVuY3kgcGxvdCB3ZSB3aWxsIGFsc28gd2FudCB0byBsb29rIGF0IGZvY3VzaW5nIG9uIHRoZSBsb3dlciBxdWFudGlsZXMuDQoNClRvIGRvOg0KDQoxKSBFc3RhYmxpc2ggaW5jb21lIGJhbmR3aXRocyBhbmQgYXNzb2NpYXRlIGJhY2sgdG8gbWFpbiBkYXRhdGFibGUuDQoNCjIpIENvbnRpbnVlIHdpdGggZnJlcXVlbmN5IHJldmllIGFuZCBjb21wYXJpc29uIG9mIHZhcmlvdXMgdmFyaWFibGVzLg0KDQozKSBDb3JyZWN0IGNsdXN0ZXJpbmcgYXMgcGFydCBvZiB0aGUgZXZhbHVhdGlvbi4gRXhhbXBsZSBiZWxvdyBkb2VzIG5vdCBoZWxwIGdpdmVuIGl0IGNhbm5vdCBiZSByZWFkLg0KDQpgYGB7ciB3YXJuaW5nPUZBTFNFfQ0KbGlicmFyeShwdmNsdXN0KQ0KZCA8LSBkaXN0KGtzaV9zZiwgbWV0aG9kID0gImV1Y2xpZGVhbiIpDQpmaXQgPC0gaGNsdXN0KGQsIG1ldGhvZD0id2FyZC5EMiIpDQpwbG90KGZpdCkNCmdyb3VwcyA8LSBjdXRyZWUoZml0LCBrPTUpDQpyZWN0LmhjbHVzdChmaXQsIGs9NSwgYm9yZGVyPSJyZWQiKQ0KYGBgDQoNCjQpIFJldmlldyBob3cgdG8gYmluZCBtYXJrZXJzIGluIGxlYWZsZXQgdG8gdGhlaXIgcG9seWdvbihzKS4gQ3VycmVudGx5IHVuY2xlYXIgaWYgdGhpcyBmZWF0dXJlIGlzIGF2YWlsYWJsZSBpbiBSc3R1ZGlvIG9yIG9ubHkgYXZhaWxhYmxlIHVzaW5nIHRoZSBsZWFmbGV0IGNvZGUgaHRtbCBvciBsZWFmbGV0IGluIHB5dGhvbg0KDQpgYGB7cn0NCkxjb250ZW50IDwtIHBhc3RlKCJZZWFyOiIsIGtzaV9jb29yZEBkYXRhJFlFQVIsDQogICAgICAgICAgICAgICAgICAiSW5jb21lOiIsa3NpX2Nvb3JkQGRhdGEkSG91c2Vob2xkLkF2ZXJhZ2UuSW5jb21lLCANCiAgICAgICAgICAgICAgICAgICJMb2NhdGlvbjoiLA0KICAgICAgICAgICAgICAgICAga3NpX2Nvb3JkQGRhdGEkU1RSRUVUMSkNCg0KbGVhZmxldChrc2lfY29vcmQpICU+JQ0KICBhZGRUaWxlcygpICU+JQ0KICBhZGRDaXJjbGVNYXJrZXJzKA0KICAgIHN0cm9rZSA9IEZBTFNFLCBmaWxsT3BhY2l0eSA9IDAuNSx3ZWlnaHQgPSAxLA0KICAgIGxhYmVsID0gfmFzLmNoYXJhY3RlcihMY29udGVudCksDQogICAgY2x1c3Rlck9wdGlvbnMgPSBtYXJrZXJDbHVzdGVyT3B0aW9ucygpKSAlPiUNCiAgYWRkUG9seWdvbnMoZGF0YSA9IG5iaF9tb2RpZmllZCwNCiAgICAgICAgICAgICAgZmlsbENvbG9yID0gInRyYW5zcGFyZW50IiwgDQogICAgICAgICAgICAgIGNvbG9yID0gIiMwMDAwMDAiLA0KICAgICAgICAgICAgICBmaWxsT3BhY2l0eSA9IDAuOCwNCiAgICAgICAgICAgICAgZ3JvdXAgPSAiTmVpZ2hib3VyaG9vZCIsIA0KICAgICAgICAgICAgICB3ZWlnaHQgPSAyKSAlPiUNCiAgYWRkUG9seWdvbnMoZGF0YSA9IGN0ciwNCiAgICAgICAgICAgICAgZmlsbENvbG9yID0gInRyYW5zcGFyZW50IiwgDQogICAgICAgICAgICAgIGNvbG9yID0gIiMwMDAwMDAiLA0KICAgICAgICAgICAgICBmaWxsT3BhY2l0eSA9IDAuOCwNCiAgICAgICAgICAgICAgZ3JvdXAgPSAiQ2Vuc3VzIFRyYWN0IiwgDQogICAgICAgICAgICAgIHdlaWdodCA9IC44KSAlPiUNCiAgYWRkUG9seWdvbnMoZGF0YSA9IGRpc2VtLA0KICAgICAgICAgICAgICBmaWxsQ29sb3IgPSAidHJhbnNwYXJlbnQiLCANCiAgICAgICAgICAgICAgY29sb3IgPSAiIzAwMDAwMCIsDQogICAgICAgICAgICAgIGZpbGxPcGFjaXR5ID0gMC44LA0KICAgICAgICAgICAgICBncm91cCA9ICJEaXNlbWluYXRpb24gQXJlYSIsIA0KICAgICAgICAgICAgICB3ZWlnaHQgPSAuNCkgJT4lDQogIGFkZExheWVyc0NvbnRyb2wob3ZlcmxheUdyb3VwcyA9YygiTmVpZ2hib3VyaG9vZCIsICJDZW5zdXMgVHJhY3QiLCAiRGlzZW1pbmF0aW9uIEFyZWEiKSwNCiAgICAgICAgICAgICAgICAgICBvcHRpb25zID0gbGF5ZXJzQ29udHJvbE9wdGlvbnMoY29sbGFwc2VkPUZBTFNFKSkNCg0KYGBgDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg==